use std::collections::VecDeque;
use std::pin::Pin;
use std::task::{Context, Poll};

use async_std::prelude::Stream;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use reqwest::header::HeaderMap;
use serde_json::Value;
use sqlx::{Pool, query, Sqlite};
use sqlx::sqlite::SqliteQueryResult;
use tracing::error;
use fast_spider_derive::db_derive::CRUD;

use crate::http::Session;
use crate::models::bili::album::BiliAlbumModel;
use crate::models::bili::user::BiliUserModel;
use crate::spider::bili::api::album_list_api;
use crate::spider::bili::BiliLoginCredential;
use crate::spider::bili::user::BiliUserSpider;
use crate::utils::pbar::PB_CRAWL_STYLE;

pub struct BiliAlbumSpider {
    session: Session<BiliLoginCredential>,
    cache: VecDeque<BiliAlbumModel>,
    mid: i64,
    page_num: i64,
    page_size: i64,
    last_upload_time: i64,
}

impl Stream for BiliAlbumSpider {
    type Item = BiliAlbumModel;

    fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        // 还有已缓存的动态
        if let Some(album) = self.cache.pop_front() {
            return if album.ctime > self.last_upload_time {
                Poll::Ready(Some(album))
            } else {
                Poll::Ready(None)
            };
        };
        // 没有已缓存的动态
        let api = match album_list_api(self.mid, self.page_num, self.page_size) {
            Ok(url) => url,
            Err(err) => {
                error!("{}", err);
                return Poll::Ready(None);
            }
        };
        self.page_num += 1;
        let resp = match self.session.request(api, reqwest::Method::GET, HeaderMap::new()) {
            Ok(resp) => resp,
            Err(err) => {
                error!("{}", err);
                return Poll::Ready(None);
            }
        };
        let json = match resp.json::<Value>() {
            Ok(json) => json,
            Err(err) => {
                error!("{}", err);
                return Poll::Ready(None);
            }
        };

        let data = match json.get("data") {
            Some(data) => data,
            None => return Poll::Ready(None)
        };

        // 获取 items 数据
        let items = match data.get("items") {
            Some(items) => {
                if items.is_array() {
                    items.as_array().unwrap()
                } else {
                    return Poll::Ready(None);
                }
            }
            None => return Poll::Ready(None)
        };
        // 将 items JSON 序列化为 BiliAlbumModel
        if items.is_empty() { return Poll::Ready(None); }
        items.iter()
            .map(BiliAlbumModel::from)
            .for_each(|album| {
                self.cache.push_back(album);
            });
        Poll::Ready(self.cache.pop_front())
    }
}

impl Iterator for BiliAlbumSpider {
    type Item = BiliAlbumModel;

    fn next(&mut self) -> Option<Self::Item> {
        // 还有已缓存的动态
        if let Some(album) = self.cache.pop_front() {
            return if album.ctime > self.last_upload_time {
                Some(album)
            } else {
                None
            };
        };
        // 没有已缓存的动态
        let api = match album_list_api(self.mid, self.page_num, self.page_size) {
            Ok(url) => url,
            Err(err) => {
                error!("{}", err);
                return None;
            }
        };
        self.page_num += 1;
        let resp = match self.session.request(api, reqwest::Method::GET, HeaderMap::new()) {
            Ok(resp) => resp,
            Err(err) => {
                error!("{}", err);
                return None;
            }
        };
        let json = match resp.json::<Value>() {
            Ok(json) => json,
            Err(err) => {
                error!("{}", err);
                return None;
            }
        };

        let data = match json.get("data") {
            Some(data) => data,
            None => return None,
        };

        // 获取 items 数据
        let items = match data.get("items") {
            Some(items) => {
                if items.is_array() {
                    items.as_array().unwrap()
                } else {
                    return None;
                }
            }
            None => return None,
        };
        // 将 items JSON 序列化为 BiliAlbumModel
        if items.is_empty() { return None; }
        items.iter()
            .map(BiliAlbumModel::from)
            .for_each(|album| {
                self.cache.push_back(album);
            });
        self.cache.pop_front()
    }
}

impl BiliAlbumSpider {
    pub async fn new(mid: i64) -> Self {
        Self {
            session: Session::new(BiliLoginCredential::default()),
            cache: VecDeque::new(),
            mid,
            page_num: 0,
            page_size: 30,
            last_upload_time: 0,
        }
    }
}

pub async fn crawl_bili_album(db: &Pool<Sqlite>) -> anyhow::Result<SqliteQueryResult>
{
    // 获取哔哩哔哩用户数据
    let mut user_spider = BiliUserSpider::new(260888);
    let users = BiliUserModel::read_all(db).await;
    let mut query_result: Vec<SqliteQueryResult> = vec![];
    // 初始化进度条
    let multi_progress = MultiProgress::new();
    let user_pbar = multi_progress.add(ProgressBar::new(users.len() as u64));
    user_pbar.set_style(PB_CRAWL_STYLE.clone());
    user_pbar.set_prefix("- 哔哩用户:");

    for user in users {
        user_pbar.inc(1);
        // 更新哔哩哔哩动态列表
        let qr = crawl_bili_albums_from_user(db, &user, false, &multi_progress).await?;
        // 更新用户信息
        user_spider.update_user_local_info(db, user.mid).await?;
        query_result.push(qr);
    }
    // 清理进度条输出
    user_pbar.finish_and_clear();
    multi_progress.clear().unwrap();
    // 统计数据库操作情况
    let mut total_qr = SqliteQueryResult::default();
    total_qr.extend(query_result);
    Ok(total_qr)
}

pub async fn crawl_bili_albums_from_user(db: &Pool<Sqlite>, u: &BiliUserModel, scan_all: bool, mpb: &MultiProgress)
                                         -> anyhow::Result<SqliteQueryResult> {
    // 初始化进度条
    let pb = mpb.add(ProgressBar::new(u.album_count as u64));
    pb.set_style(PB_CRAWL_STYLE.clone());
    pb.set_prefix("- 动    态：");
    // 初始化数据
    let latest_ctime = match BiliAlbumModel::select_latest_album(db).await {
        Some(album) => if scan_all { 0 } else { album.ctime },
        None => 0,
    };
    let mut cache: Vec<BiliAlbumModel> = vec![];

    for album in BiliAlbumSpider::new(u.mid).await {
        if scan_all || album.ctime > latest_ctime {
            cache.push(album);
            pb.inc(1);
        } else {
            pb.set_length(u.album_count as u64);
            break;
        }
    }

    let res = BiliAlbumModel::insert_batch(db, &cache, cache.len()).await?;
    pb.finish_and_clear();
    Ok(res)
}
